home *** CD-ROM | disk | FTP | other *** search
/ Super PC 34 / Super PC 34 (Shareware).iso / spc / UTIL / DJGPP2 / V2 / DJLSR200.ZIP / src / libc / posix / sys / stat / fstat.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-01-24  |  31.4 KB  |  853 lines

  1. /* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */
  2. /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */
  3. /* This is file FSTAT.C */
  4. /*
  5.  *   Almost a 100% U**X-compatible fstat() substitute.
  6.  *
  7.  * Usage:
  8.  *
  9.  *   That's easy: just put this in libc.a, and then call fstat() as usual.
  10.  *
  11.  * Rationale:
  12.  *
  13.  *   Many Unix-born programs make heavy use of fstat() library
  14.  *   function to make decisions on files' equality, size, access
  15.  *   attributes etc.  In the MS-DOS environment, many implementations
  16.  *   of fstat() are crippled, because DOS makes it very hard to get to
  17.  *   certain pieces of information about files and directories.  Thus
  18.  *   porting a program to DOS is usually an exercise in #ifdef'ing.
  19.  *   This implementation facilitates porting Unix programs to MS-DOS
  20.  *   by providing an fstat() which is much more Unix-compatible than
  21.  *   those of most DOS-based C compilers (e.g., Borland's).
  22.  *   Specifically, the following issues are taken care of:
  23.  *
  24.  *      1. Mode bits are returned for the actual file, files are NOT
  25.  *         reported read-only (as in Borland's library fstat()).
  26.  *      2. Mode bits are set for all 3 groups (user, group, other).
  27.  *      3. Device code (st_dev, st_rdev) is correctly reported (0 = 'A',
  28.  *         1 = 'B' etc.).
  29.  *      4. Character device names (such as /dev/con, lpt1, aux etc.) are
  30.  *         treated as if they were on a special drive called `@:'
  31.  *         (st_dev = -1).  The "character special" mode bit is set
  32.  *         for these devices.
  33.  *      5. The inode number (st_ino) is taken from the starting cluster
  34.  *         number of the file.  If the cluster number is unavailable, it
  35.  *         is invented using the file's name in a manner that minimizes
  36.  *         the possibility of inventing an inode which already belongs
  37.  *         to another file.  See below for details.
  38.  *      6. Executable files are found based on files' extensions and
  39.  *         magic numbers present at their beginning, and their execute
  40.  *         bits are set.
  41.  *
  42.  *   Lossage:
  43.  *
  44.  *      Beautiful as the above sounds, this implementation does fail
  45.  *      under certain circumstances.  The following is a list of known
  46.  *      problems:
  47.  *
  48.  *      1. Files open on networked drives mounted by Novell Netware
  49.  *         before revision 4.x cannot be traced using DOS System File
  50.  *         Table.  Therefore, name, extension, file attributes and the
  51.  *         drive letter are not available for these.  Until somebody
  52.  *         tells me how this information can be obtained under Novell,
  53.  *         nothing could be done here.  For the time being, these files
  54.  *         will get st_dev of -2.
  55.  *      2. For files which reside on networked drives, the inode number
  56.  *         is always invented, because network redirectors usually do
  57.  *         not bring that info with them.
  58.  *      3. Empty files do not have a starting cluster number, because
  59.  *         DOS doesn't allocate one until you actually write something
  60.  *         to a file.  For these the inode is also invented.
  61.  *      4. If the st_ino field is a 16 bit number, the invented inode
  62.  *         numbers are from 65535 and down, assuming that most disks have
  63.  *         unised portions near their end.  Valid cluster numbers are 16-bit
  64.  *         unisgned integers, so a possibility of a clash exists, although
  65.  *         the last 80 or more cluster numbers are unused on all drives
  66.  *         I've seen.  If the st_ino is 32 bit, then invented inodes are
  67.  *         all greater than 64k, which totally eliminates a possibility
  68.  *         of a clash with an actual cluster number.
  69.  *      5. As this implementation relies heavily on undocumented DOS
  70.  *         features, it will fail to get actual file info in environments
  71.  *         other than native DOS, such as DR-DOS, OS/2 etc.  For these,
  72.  *         the function will return whatever info is available with
  73.  *         conventional DOS calls, which is no less than any other
  74.  *         implementation could do.  This fstat() might also fail for
  75.  *         future DOS versions, if the layout of DOS System File Table
  76.  *         is changed; however, this seems unlikely.
  77.  *
  78.  * Copyright (c) 1994-1996 Eli Zaretskii <eliz@is.elta.co.il>
  79.  *
  80.  * This software may be used freely so long as this copyright notice is
  81.  * left intact.  There is no warranty on this software.
  82.  *
  83.  */
  84.  
  85. /*
  86.  * Tested with DJGPP port of GNU C compiler, versions 1.11maint5 and 1.12m2,
  87.  * under MS-DOS 3.3, 4.01, 5.0, 6.20 (with and without DoubleSpace) and
  88.  * with networked drives under XFS 1.76, Novell Netware 3.22, and
  89.  * TSoft NFS 0.24Beta.
  90.  *
  91.  */
  92.  
  93. #include <libc/stubs.h>
  94. #include <string.h>
  95. #include <stdlib.h>
  96. #include <stdio.h>
  97. #include <errno.h>
  98. #include <time.h>
  99. #include <unistd.h>
  100. #include <sys/types.h>
  101. #include <sys/stat.h>
  102.  
  103. #include <dpmi.h>
  104. #include <go32.h>
  105. #include <libc/farptrgs.h>
  106. #include <libc/bss.h>
  107.  
  108. #include "xstat.h"
  109.  
  110. #define _STAT_INODE         1   /* should we bother getting inode numbers? */
  111. #define _STAT_EXEC_EXT      2   /* get execute bits from file extension? */
  112. #define _STAT_EXEC_MAGIC    4   /* get execute bits from magic signature? */
  113. #define _STAT_DIRSIZE       8   /* compute directory size? */
  114. #define _STAT_ROOT_TIME  0x10   /* try to get root dir time stamp? */
  115. #define _STAT_WRITEBIT   0x20   /* fstat() needs write bit? */
  116.  
  117. /* Should we bother about executables at all? */
  118. #define _STAT_EXECBIT       (_STAT_EXEC_EXT | _STAT_EXEC_MAGIC)
  119.  
  120. /* Should we bother about any access bits?  */
  121. #define _STAT_ACCESS        (_STAT_WRITEBIT | _STAT_EXECBIT)
  122.  
  123. /* Do we need SFT info at all? */
  124. #define _STAT_NEEDS_SFT     (_STAT_WRITEBIT | _STAT_EXEC_EXT | _STAT_INODE)
  125.  
  126. /*
  127.  * Lower-level assist functions to get a starting cluster of a file,
  128.  * which will serve as an inode number.  The starting cluster is
  129.  * found in the System File Table entry (an internal structure of
  130.  * DOS) which belongs to our file.
  131.  *
  132.  * Much of the following code is derived from file H2NAME.C, which
  133.  * came with ``Undocumented DOS'', 1st edition.
  134.  */
  135.  
  136. /* Selectors for conventional memory and our program's memory.  */
  137. static unsigned short dos_mem_base, our_mem_base;
  138.  
  139. /* Array of SFT entry sizes as function of DOS version. */
  140. static size_t sft_size_list[] = {0, 0x28, 0x35, 0x3b};
  141.  
  142. /* Actual size of SFT entry for the version of DOS under
  143.    which we are running.  */
  144. static size_t sft_size;
  145.  
  146. /* Static array to hold a copy of SFT entry.  Should be at
  147.    least as long as the largest number in sft_size_list[].  */
  148. static unsigned char sft_buf[0x40];
  149.  
  150. /* Linear address of pointer to Job File Table (JFT) which
  151.    is an array of indices into the SFT which correspond to
  152.    open file handles.  */
  153. static unsigned long htbl_ptr_addr;
  154.  
  155. /* True version of DOS (not the one simulated by SETVER).  */
  156. static unsigned short dos_major, dos_minor;
  157.  
  158. /* Segment and offset of first SFT sub-table.  All searches
  159.    start from this address.  */
  160. static unsigned short sft_start_seg, sft_start_off;
  161.  
  162. /* This holds the failure bits from last call to fstat_init(),
  163.    so we can return them each time fstat_assist() is called.  */
  164. static unsigned short fstat_init_bits;
  165.  
  166. /* Holds the last seen value of __bss_count, to be safe for
  167.    restarted programs (emacs).  */
  168. static int fstat_count = -1;
  169.  
  170. /* Initialization routine, called once per program run.
  171.  * Finds DOS version, SFT entry size and addresses of
  172.  * program handle table and first SFT sub-table.
  173.  */
  174. static int
  175. fstat_init(void)
  176. {
  177.   __dpmi_regs    regs;
  178.   int            sft_ptr_addr;
  179.   unsigned short true_dos_version;
  180.  
  181.   /* Selectors for _farXXX() functions.  */
  182.   dos_mem_base = _dos_ds;
  183.   our_mem_base = _my_ds();
  184.  
  185.   /* Each DOS program has a table of file handles which are used by
  186.    * DOS open() call.  This table holds, for each handle which is in
  187.    * use, the index into the System File Tables' list which contains
  188.    * data about this handle.  The pointer to that handle table is found
  189.    * at offset 34h in the program's PSP.
  190.    */
  191.  
  192.   /* Linear address of pointer to the handle table.  We postpone
  193.    * dereferencing this pointer to obtain the address of the handle
  194.    * table until we're actually called for a specific handle, because
  195.    * somebody could in the meanwhile change that address, e.g. by
  196.    * calling INT 21h/AX=67h to enlarge the maximum number of file
  197.    * handles.
  198.    */
  199.   htbl_ptr_addr = (_go32_info_block.linear_address_of_original_psp & 0xfffff)
  200.                   + 0x34;
  201.  
  202.   /*
  203.    * Find the pointer to the first subtable in the list of SFT's.
  204.    * It is stored at offset 4 in the DOS List-of-Lists area, a ptr
  205.    * to which is returned by the undocumented DOS function 52h.
  206.    * We don't check FLAGS after 52h returns because Ralph Brown's
  207.    * Interrupt List doesn't say FLAGS are set to indicate a
  208.    * failure.
  209.    */
  210.   regs.h.ah = 0x52;
  211.   __dpmi_int(0x21, ®s);
  212.  
  213.   /* Linear addres of pointer to SFT list.  */
  214.   sft_ptr_addr = MK_FOFF(regs.x.es, regs.x.bx + 4);
  215.   
  216.   /* SFT entry size depends on DOS version.
  217.      We need exact knowledge about DOS internals, so we need the
  218.      TRUE DOS version (not the simulated one by SETVER), if that's
  219.      available.  */
  220.   true_dos_version = _get_dos_version(1);
  221.   dos_major = true_dos_version >> 8;
  222.   dos_minor = true_dos_version & 0xff;
  223.   sft_size = sft_size_list[dos_major > 4 ? 3 : dos_major - 1];
  224.   if (!sft_size)        /* unsupported DOS version */
  225.     {
  226.       _djstat_fail_bits |= _STFAIL_OSVER;
  227.       return 0;
  228.     }
  229.  
  230.   /* Segment and offset of start of SFT list.  */
  231.   sft_start_off = _farpeekw(dos_mem_base, sft_ptr_addr);
  232.   sft_start_seg = _farpeekw(dos_mem_base, sft_ptr_addr + 2);
  233.  
  234.   return 1;
  235. }
  236.  
  237. /* Given a handle, copy contents of System File Table entry which
  238.  * belongs to that handle into a local buffer, and return the index
  239.  * into the SFT array where our entry was found.  In case of failure
  240.  * to use SFT, return -2.  If FHANDLE is illegal, return -1.
  241.  */
  242. static short
  243. get_sft_entry(int fhandle)
  244. {
  245.   unsigned long  sft_seg;
  246.   unsigned short sft_off;
  247.   unsigned long  htbl_addr;
  248.   short          sft_idx, retval;
  249.  
  250.   _djstat_fail_bits = fstat_init_bits;
  251.  
  252.   /* Force initialization if we were restarted (emacs).  */
  253.   if (fstat_count != __bss_count)
  254.     {
  255.       fstat_count = __bss_count;
  256.       dos_major = 0;
  257.     }
  258.  
  259.   /* If first time called, initialize.  */
  260.   if (!dos_major && !fstat_init())
  261.     {
  262.       fstat_init_bits = _djstat_fail_bits;
  263.       return -2;
  264.     }
  265.  
  266.   /* Test file handle for validity.
  267.    * For DOS 3.x and later, the number of possible file handles
  268.    * is at offset 32h in the PSP; for prior versions, it is 20.
  269.    */
  270.   if (fhandle < 0 ||
  271.       fhandle >=
  272.         (_osmajor < 3 ?
  273.          20 :
  274.          _farpeekw(dos_mem_base,
  275.                    (_go32_info_block.linear_address_of_original_psp & 0xfffff)
  276.                    + 0x32)))
  277.       return -1;
  278.  
  279.   /* Linear address of the handle table. */
  280.   htbl_addr = MK_FOFF(_farpeekw(dos_mem_base, htbl_ptr_addr + 2),
  281.                       _farpeekw(dos_mem_base, htbl_ptr_addr));
  282.  
  283.   /* Index of the entry for our file handle in the SFT array.  */
  284.   retval = sft_idx = _farpeekb(dos_mem_base, htbl_addr + fhandle);
  285.  
  286.   if (sft_idx < 0)      /* invalid file handle or bad handle table */
  287.     {
  288.       _djstat_fail_bits |= _STFAIL_SFTIDX;
  289.       return -1;
  290.     }
  291.  
  292.   /* Given the index into the SFT list, find our SFT entry.
  293.    * The list consists of arrays (sub-tables) of entries, each sub-
  294.    * table preceeded by a header.  The header holds a pointer to the
  295.    * next sub-table in the list and number of entries in this sub-table.
  296.    * The list is searched until the sub-table which contains our
  297.    * target is found, then the sub-table entries are skipped until
  298.    * we arrive at our target.
  299.    */
  300.  
  301.   /* Segment (shifted 4 bits left) and offset of start of SFT list.  */
  302.   sft_off = sft_start_off;
  303.   sft_seg = MK_FOFF(sft_start_seg, 0);
  304.  
  305.   while (sft_off != 0xFFFF)
  306.     {
  307.       unsigned long entry_addr   = sft_seg + sft_off;
  308.       short         subtable_len = _farpeekw(dos_mem_base, entry_addr + 4);
  309.  
  310.       if (sft_idx < subtable_len)
  311.         { /* Our target is in this sub-table.  Pull in the entire
  312.            * SFT entry for use by fstat_assist().
  313.            */
  314.           movedata(dos_mem_base,
  315.                    (entry_addr + 6 + sft_idx * sft_size) & 0x000fffff,
  316.                    our_mem_base, (unsigned int)sft_buf, sft_size);
  317.           return retval;
  318.         }
  319.       /* Our target not in this subtable.
  320.        * Subtract the number of entries in this sub-table from the
  321.        * index of our entry, and proceed to next sub-table.
  322.        */
  323.       sft_idx -= subtable_len;
  324.       sft_off  = _farpeekw(dos_mem_base, entry_addr);
  325.       sft_seg  = MK_FOFF(_farpeekw(dos_mem_base, entry_addr + 2), 0);
  326.     }
  327.  
  328.   /* Get here only by error, which probably means unsupported DOS version. */
  329.   _djstat_fail_bits |= _STFAIL_SFTNF;
  330.   return -2;
  331. }
  332.  
  333. /* fstat_assist() is where all the actual work is done.
  334.  * It uses SFT entry, if available and its contents are verified.
  335.  * Otherwise, it finds all the available info by conventional
  336.  * DOS calls.
  337.  */
  338.  
  339. static int
  340. fstat_assist(int fhandle, struct stat *stat_buf)
  341. {
  342.   short          have_trusted_values = 1;
  343.   unsigned int   dos_ftime;
  344.   char           drv_no;
  345.   unsigned char  is_dev;
  346.   unsigned char  is_remote;
  347.   short          sft_idx = -1;
  348.   unsigned short sft_fdate, sft_ftime;
  349.   long           sft_fsize;
  350.   unsigned short trusted_ftime = 0, trusted_fdate = 0;
  351.   long           trusted_fsize = 0;
  352.  
  353.   _djstat_fail_bits = 0;
  354.  
  355.   /* Get pointer to an SFT entry which holds data for our handle. */
  356.   if ( (_djstat_flags & _STAT_NEEDS_SFT) == 0 &&
  357.        (sft_idx = get_sft_entry(fhandle)) == -1)
  358.     {
  359.       errno = EBADF;
  360.       return -1;
  361.     }
  362.  
  363.   /* Initialize buffers. */
  364.   memset(stat_buf, 0, sizeof(struct stat));
  365.   dos_ftime = 0;
  366.  
  367.   /* Get some info about this handle by conventional DOS calls.  These
  368.    * will serve as verification of SFT entry contents and also as
  369.    * fall-back in case SFT method fails.
  370.    */
  371.   if (_getftime(fhandle, &dos_ftime) == 0 &&
  372.       (trusted_fsize = __filelength(fhandle)) != -1L)
  373.     {
  374.       trusted_ftime = dos_ftime & 0xffff;
  375.       trusted_fdate = dos_ftime >> 16;
  376.     }
  377.   else
  378.     have_trusted_values = 0;
  379.  
  380.   /* First, fill the fields which are constant under DOS. */
  381.   stat_buf->st_uid = getuid();
  382.   stat_buf->st_gid = getgid();
  383.   stat_buf->st_nlink = 1;
  384. #ifndef  NO_ST_BLKSIZE
  385.   stat_buf->st_blksize = _go32_info_block.size_of_transfer_buffer;
  386. #endif
  387.  
  388.   /* If SFT entry for our handle is required and available, we will use it.  */
  389.   if ( (_djstat_flags & _STAT_NEEDS_SFT) == 0 && sft_idx >= 0)
  390.     {
  391.       /* Determine positions of data items in the SFT. */
  392.       size_t fattr_ofs, name_ofs, ext_ofs, fsize_ofs, fdate_ofs,
  393.              ftime_ofs, clust_ofs;
  394.  
  395.       switch (dos_major)
  396.         {
  397.           case 2:
  398.               fattr_ofs  = 2;
  399.               drv_no     = sft_buf[3] - 1;      /* 1 = 'A' etc. */
  400.               is_dev     = drv_no < 0;          /* sft_buf[3] = 0 */
  401.               name_ofs   = 4;
  402.               ext_ofs    = 0x0b;
  403.               fsize_ofs  = 0x13;
  404.               fdate_ofs  = 0x17;
  405.               ftime_ofs  = 0x19;
  406.               clust_ofs  = 0x1c;
  407.               is_remote  = 0;   /* DOS 2.x didn't have remote files */
  408.               break;
  409.  
  410.           case 3:
  411.               fattr_ofs  = 4;
  412.               drv_no     = sft_buf[5] & 0x3f;
  413.               is_dev     = sft_buf[5] & 0x80;
  414.               is_remote  = sft_buf[6] & 0x80;
  415.               if (dos_minor == 0)
  416.                 {
  417.                   name_ofs = 0x21;
  418.                   ext_ofs  = 0x29;
  419.                 }
  420.               else      /* DOS 3.1 - 3.3x */
  421.                 {
  422.                   name_ofs = 0x20;
  423.                   ext_ofs  = 0x28;
  424.                 }
  425.               clust_ofs  = 0x0b;
  426.               ftime_ofs  = 0x0d;
  427.               fdate_ofs  = 0x0f;
  428.               fsize_ofs  = 0x11;
  429.               break;
  430.  
  431.           default:      /* DOS 4 and up */
  432.               fattr_ofs  = 4;
  433.               drv_no     = sft_buf[5] & 0x3f;
  434.               is_dev     = sft_buf[5] & 0x80;
  435.               is_remote  = sft_buf[6] & 0x80;
  436.               clust_ofs  = 0x0b;
  437.               ftime_ofs  = 0x0d;
  438.               fdate_ofs  = 0x0f;
  439.               fsize_ofs  = 0x11;
  440.               name_ofs   = 0x20;
  441.               ext_ofs    = 0x28;
  442.  
  443.         }
  444.  
  445.       if (is_dev)
  446.         {
  447.           /* We have a character device.
  448.            * We will pretend as if they all reside on a special
  449.            * drive `@:', which is illegal in DOS, and just happens
  450.            * to give a neat st_dev (= '@' - 'A') = -1.
  451.            */
  452.  
  453.           stat_buf->st_dev = -1;
  454. #ifdef  HAVE_ST_RDEV
  455.           stat_buf->st_rdev = -1;
  456. #endif
  457.  
  458.           if ( (_djstat_flags & _STAT_INODE) == 0 )
  459.             {
  460.               /* Character device names are all at most 8-character long. */
  461.               short i   = 8;
  462.               char *src = sft_buf + name_ofs;
  463.               char dev_name[16], *dst = dev_name + 7;
  464.  
  465.               strcpy(dev_name, "@:\\dev\\        "); /* pad with 8 blanks */
  466.               while (i-- && *src != ' ')             /* copy non-blank chars */
  467.                 *dst++ = *src++;
  468.  
  469.               stat_buf->st_ino = _invent_inode(dev_name, 0, 0);
  470.             }
  471.  
  472.           /* Should we make printer devices write-only here? */
  473.           stat_buf->st_mode |= (S_IFCHR | READ_ACCESS | WRITE_ACCESS);
  474.  
  475.           /* We will arrange things so that devices have current time in
  476.            * the access-time and modified-time fields of struct stat.
  477.            */
  478.           stat_buf->st_atime = stat_buf->st_mtime = time(0);
  479.  
  480.           /* MS-DOS returns the time of boot when _getftime() is called
  481.            * for character devices, but this is undocumented and
  482.            * unsupported by DOS clones (e.g. DR-DOS).  It is also
  483.            * inconsistent with our stat().  Therefore, we will arrange
  484.            * things so that devices have zero (the beginning of times)
  485.            * in creation-time field.
  486.            */
  487.           dos_ftime = 0;
  488.           stat_buf->st_ctime = _file_time_stamp(dos_ftime);
  489.  
  490.           return 0;
  491.         }
  492.  
  493.       /* Files are not allowed to fail DOS calls for their time
  494.        * stamps and size.
  495.        */
  496.       else if (have_trusted_values)
  497.         {
  498.           /* This is a regular, existing file.  It cannot be a
  499.            * directory, because DOS won't let us open() a directory.
  500.            * Each file under MS-DOS is always readable by everyone.
  501.            */
  502.           stat_buf->st_mode |= (S_IFREG | READ_ACCESS);
  503.           
  504.           /* We will be extra careful in trusting SFT data: it must be
  505.            * consistent with date, time and size of the file as known
  506.            * from conventional DOS calls.
  507.            */
  508.           sft_fdate = *((unsigned short *)(sft_buf + fdate_ofs));
  509.           sft_ftime = *((unsigned short *)(sft_buf + ftime_ofs));
  510.           sft_fsize = *((long *)(sft_buf + fsize_ofs));
  511.           if (sft_ftime == trusted_ftime &&
  512.               sft_fdate == trusted_fdate &&
  513.               sft_fsize == trusted_fsize)
  514.             { /* Now we are ready to get the SFT info. */
  515.               char           sft_extension[4], *dst = sft_extension + 2;
  516.               unsigned char *src = sft_buf + ext_ofs + 2;
  517.               int i = 3;
  518.  
  519.               /* Get the file's extension.  It is held in the SFT entry
  520.                * as a blank-padded 3-character string without terminating
  521.                * zero.  Some crazy files have embedded blanks in their
  522.                * extensions, so only TRAILING blanks are insignificant.
  523.                */
  524.               memset(sft_extension, 0, sizeof(sft_extension));;
  525.               while (*src == ' ' && i--)    /* skip traling blanks */
  526.                 {
  527.                   dst--; src--;
  528.                 }
  529.  
  530.               if (i >= 0)
  531.                 while (i--)                 /* move whatever left */
  532.                   *dst-- = *src--;
  533.  
  534.               /* Build Unix-style file permission bits. */
  535.               if ( !(sft_buf[fattr_ofs] & 0x07) ) /* no R, S or H bits set */
  536.                 stat_buf->st_mode |= WRITE_ACCESS;
  537.  
  538.               /* Execute permission bits.  fstat() cannot be called on
  539.                * directories under DOS, so only executable programs/batch
  540.                * files should be considered.
  541.                */
  542.               if (_is_executable((const char *)0, fhandle, sft_extension))
  543.                 stat_buf->st_mode |= EXEC_ACCESS;
  544.  
  545.               /* DOS 4.x and above seems to know about named pipes. */
  546.               if (dos_major > 3 && (sft_buf[6] & 0x20))
  547.                 stat_buf->st_mode |= S_IFIFO;
  548.  
  549.               /* Device code. */
  550.               stat_buf->st_dev = drv_no;
  551. #ifdef  HAVE_ST_RDEV
  552.               stat_buf->st_rdev = drv_no;
  553. #endif
  554.  
  555.               /* The file's starting cluster number will serve as its
  556.                * inode number.
  557.                */
  558.               if ( (_djstat_flags & _STAT_INODE) == 0 && !is_remote)
  559.                 stat_buf->st_ino = *((unsigned short *)(sft_buf + clust_ofs));
  560.  
  561.               /* If the cluster number returns zero (e.g., for empty files,
  562.                * because DOS didn't allocate it a cluster yet) we have to
  563.                * invent the inode using the file's name.  We will use the
  564.                * index into the SFT as part of unique identifier for our
  565.                * file, so a possibility of two files with the same name
  566.                * but different paths getting the same inode number is
  567.                * minimized.
  568.                * If we have a remote file, we invent inode even if there
  569.                * is a non-zero number in the SFT, because it usually is
  570.                * bogus (a left-over from last local file handle which used
  571.                * the same SFT entry).
  572.                * Note that we invent the inode even if is_remote is -1
  573.                * (i.e., IOCTL Func 0Ah failed), because that should mean
  574.                * some network redirector grabs IOCTL functions in an
  575.                * incompatible way.
  576.                */
  577.               if ( (_djstat_flags & _STAT_INODE) == 0 &&
  578.                    (stat_buf->st_ino == 0 || is_remote))
  579.                 {
  580.                   static char     name_pat[]   = " :sft-   \\            ";
  581.                   char name_buf[sizeof(name_pat)];
  582.                   unsigned char  *src_p        = sft_buf + name_ofs + 7;
  583.                   char           *dst_p        = name_buf + 17;
  584.                   int             j            = 8;
  585.                   char           *name_end;
  586.                   int             first_digit  = sft_idx / 100;
  587.                   int             second_digit = (sft_idx - first_digit * 100) / 10;
  588.                   int             third_digit  = sft_idx - first_digit * 100
  589.                                                          - second_digit * 10;
  590.  
  591.                   /* Initialize the name buffer with zeroes, then
  592.                    * put in the drive letter and ``sft-XXX'', where
  593.                    * XXX is the index of our file entry in the SFT.
  594.                    */
  595.                   strcpy(name_buf, name_pat);
  596.                   memset(name_buf + 10, 0, sizeof(name_buf) - 10);
  597.                   name_buf[0] = drv_no + 'A';
  598.                   name_buf[6] = first_digit  + '0';
  599.                   name_buf[7] = second_digit + '0';
  600.                   name_buf[8] = third_digit  + '0';
  601.  
  602.                   /* Copy filename from SFT entry to local storage.
  603.                    * It is stored there in the infamous DOS format:
  604.                    * both name and extension are blank-padded, and no dot.
  605.                    * We cannot use strcpy, because the name might
  606.                    * include embedded blanks.  Therefore we move the
  607.                    * characters from the end towards the beginning.
  608.                    */
  609.                   while (*src_p == ' ' && j--)   /* skip traling blanks */
  610.                     {
  611.                       dst_p--;
  612.                       src_p--;
  613.                     }
  614.                   name_end = dst_p + 1;
  615.  
  616.                   if (j >= 0)                  /* move whatever left */
  617.                     while (j--)
  618.                       *dst_p-- = *src_p--;
  619.  
  620.                   /* We've already got the extension.  If it is non-empty,
  621.                    * insert a dot and copy the extension itself.
  622.                    */
  623.                   if (sft_extension[0])
  624.                     {
  625.                       *name_end++ = '.';
  626.                       strcpy(name_end, sft_extension);
  627.                     }
  628.                   stat_buf->st_ino =
  629.                     _invent_inode(name_buf, dos_ftime, sft_fsize);
  630.                   _djstat_fail_bits |= _STFAIL_HASH;
  631.                 }
  632.  
  633.               /* Size, date and time. */
  634.               stat_buf->st_size = sft_fsize;
  635.               stat_buf->st_atime = stat_buf->st_ctime = stat_buf->st_mtime =
  636.                 _file_time_stamp(dos_ftime);
  637.  
  638.               return 0;
  639.             }
  640.  
  641.           _djstat_fail_bits |= _STFAIL_BADSFT;
  642.  
  643.         }
  644.  
  645.       /* Regular file, but DOS calls to find its length and time stamp
  646.        * failed.  This must be an illegal file handle, or something
  647.        * else very, very funny...
  648.        */
  649.       else
  650.         return -1;    /* errno set by filelength() or getftime() */
  651.  
  652.     }
  653.  
  654.   /* Can't get SFT itself or can't find SFT entry belonging to our file.
  655.    * This is probably unsupported variety of DOS, or other (not-so-
  656.    * compatible) OS.
  657.    * For these we supply whatever info we can find by conventional calls.
  658.    */
  659.   if (have_trusted_values)
  660.     {
  661.       short dev_info = _get_dev_info(fhandle);   /* IOCTL Function 0 */
  662.       if (dev_info == -1)
  663.         return -1;    /* errno set by get_dev_info() */
  664.  
  665.       if (dev_info & 0x80)      /* it's a device */
  666.         {
  667.           if (_djstat_flags & _STAT_INODE)
  668.             {
  669.               /* We need the name of the device to invent an inode for it.
  670.                * We cannot get the REAL name, because SFT info is unavailable.
  671.                * If IOCTL tells us this is one of the standard devices, we
  672.                * can make an educated guess.  If not, we will invent inode
  673.                * with no name.  This will at least ensure that no two calls
  674.                * accidentally get the same inode number.
  675.                * We will also pretend devices belong to a special drive
  676.                * named `@'.
  677.                */
  678.               if (dev_info & 0xf)
  679.                 {
  680.                   char dev_name[16];
  681.  
  682.                   strcpy(dev_name, "@:\\dev\\");
  683.                   if (dev_info & 3)         /* either STDIN or STDOUT */
  684.                     strcat(dev_name, "CON     ");
  685.                   else if (dev_info & 4)    /* NULL device */
  686.                     strcat(dev_name, "NUL     ");
  687.                   else if (dev_info & 8)    /* CLOCK device */
  688.                     strcat(dev_name, "CLOCK$  ");
  689.  
  690.                   stat_buf->st_ino = _invent_inode(dev_name, 0, 0);
  691.                 }
  692.               else
  693.                 stat_buf->st_ino = _invent_inode("", 0, 0);
  694.  
  695.               _djstat_fail_bits |= _STFAIL_HASH;
  696.             }
  697.  
  698.           stat_buf->st_dev = -1;
  699. #ifdef  HAVE_ST_RDEV
  700.           stat_buf->st_rdev = -1;
  701. #endif
  702.  
  703.           stat_buf->st_mode |= (S_IFCHR | READ_ACCESS | WRITE_ACCESS);
  704.  
  705.           stat_buf->st_atime = stat_buf->st_mtime = time(0);
  706.           dos_ftime = 0;
  707.           stat_buf->st_ctime = _file_time_stamp(dos_ftime);
  708.         }
  709.       else
  710.         {
  711.           /* Regular file.  The inode will be arbitrary, as we don't have
  712.            * this file's name.  Sigh...
  713.            */
  714.           if ( (_djstat_flags & _STAT_INODE) == 0 )
  715.             {
  716.               _djstat_fail_bits |= _STFAIL_HASH;
  717.               stat_buf->st_ino = _invent_inode("", dos_ftime, trusted_fsize);
  718.             }
  719.  
  720.           /* Return the minimum access bits every file has under DOS. */
  721.           stat_buf->st_mode |= (S_IFREG | READ_ACCESS);
  722.           if (_djstat_flags & _STAT_ACCESS)
  723.             _djstat_fail_bits |= _STFAIL_WRITEBIT;
  724.  
  725.           /* Executables are detected if they have magic numbers.  */
  726.           if ( (_djstat_flags & _STAT_EXECBIT) &&
  727.                _is_executable((const char *)0, fhandle, (const char *)0))
  728.             stat_buf->st_mode |= EXEC_ACCESS;
  729.  
  730.           /* Lower 6 bits of IOCTL return value give the device number. */
  731.           stat_buf->st_dev = dev_info & 0x3f;
  732. #ifdef  HAVE_ST_RDEV
  733.           stat_buf->st_rdev = dev_info & 0x3f;
  734. #endif
  735.  
  736.           /* Novell Netware returns 0 drive number in the lower
  737.            * 6 bits of dev_info.  If this is what we get, return -2
  738.            * as drive number (it will be converted to '?' if added to 'A').
  739.            */
  740.           if (stat_buf->st_dev == 0)
  741.             {
  742.               stat_buf->st_dev = -2;
  743. #ifdef  HAVE_ST_RDEV
  744.               stat_buf->st_rdev = -2;
  745. #endif
  746.               _djstat_fail_bits |= _STFAIL_DEVNO;
  747.             }
  748.  
  749.           stat_buf->st_size  = trusted_fsize;
  750.           stat_buf->st_atime = stat_buf->st_ctime = stat_buf->st_mtime =
  751.             _file_time_stamp(dos_ftime);
  752.         }
  753.       return 0;
  754.     }
  755.  
  756.   /* Don't have even values from conventional DOS calls.
  757.    * Give up completely on this funny handle.  ERRNO is already
  758.    * set by filelength() and/or getftime().
  759.    */
  760.   else
  761.     return -1;
  762. }
  763.  
  764. /*
  765.  * Main entry point.  This is a substitute for library fstat() function.
  766.  */
  767.  
  768. int
  769. fstat(int handle, struct stat *statbuf)
  770. {
  771.   int            e = errno;     /* save previous value of errno */
  772.  
  773.   if (!statbuf)
  774.     {
  775.       errno = EFAULT;
  776.       return -1;
  777.     }
  778.  
  779.   if (fstat_assist(handle, statbuf) == -1)
  780.     {
  781.       return -1;      /* already have ERRNO set by fstat_assist() */
  782.     }
  783.   else
  784.     {
  785.       errno = e;
  786.       return 0;
  787.     }
  788. }
  789.  
  790. #ifdef  TEST
  791.  
  792. #include <stdio.h>
  793. #include <fcntl.h>
  794.  
  795. unsigned short _djstat_flags = 0;
  796.  
  797. int main(int argc, char *argv[])
  798. {
  799.   struct stat stat_buf;
  800.   int fd = -1;
  801.   int i;
  802.   char *endp;
  803.  
  804.   argc--; argv++;
  805.   _djstat_flags = (unsigned short)strtoul(*argv, &endp, 0);
  806.  
  807.   /* Display 4 standard handles which are already open. */
  808.   for (i = 0; i <= 4; i++)
  809.     {
  810.       fstat(i, &stat_buf);
  811.       fprintf(stderr, "handle-%d: %d %6u %o %d %d %ld %lu %s", i,
  812.               stat_buf.st_dev,
  813.               (unsigned)stat_buf.st_ino,
  814.               stat_buf.st_mode,
  815.               stat_buf.st_nlink,
  816.               stat_buf.st_uid,
  817.               (long)stat_buf.st_size,
  818.               (unsigned long)stat_buf.st_mtime,
  819.               ctime(&stat_buf.st_mtime));
  820.       _djstat_describe_lossage(stderr);
  821.     }
  822.  
  823.   /* Now call fstat() for each command-line argument. */
  824.   while (++argv, --argc)
  825.     {
  826.       if (fd >= 19)
  827.         close(fd);
  828.       fd = open(*argv, O_RDONLY);
  829.       if (fd != -1 && !fstat(fd, &stat_buf))
  830.         {
  831.           fprintf(stderr, "%s (%d): %d %6u %o %d %d %ld %lu %s", *argv, fd,
  832.                   stat_buf.st_dev,
  833.                   (unsigned)stat_buf.st_ino,
  834.                   stat_buf.st_mode,
  835.                   stat_buf.st_nlink,
  836.                   stat_buf.st_uid,
  837.                   (long)stat_buf.st_size,
  838.                   (unsigned long)stat_buf.st_mtime,
  839.                   ctime(&stat_buf.st_mtime));
  840.           _djstat_describe_lossage(stderr);
  841.         }
  842.       else
  843.         {
  844.           fputs(*argv, stderr);
  845.           perror(": failed to open/fstat");
  846.           _djstat_describe_lossage(stderr);
  847.         }
  848.     }
  849.   return 0;
  850. }
  851.  
  852. #endif  /* TEST */
  853.